MdVerbs.php

<?php

namespace Tlf\Scrawl\Ext;

/**
 * Runs `@mdverb` extensions, enabling special callbacks for `@verb()`s in `.src.md` files
 */
class MdVerbs { 

    protected $regs = [
        /**
         * Separates an `@mdverb(arg1,arg2)` into $1=mdverb, $2=arg1,arg2
         */
        'verb' =>'/(?<!\\\\)\@([a-zA-Z_]+)\(([^\)]*)\)/m',
    ];

    /** 
     * array of verb handlers
     * 
     * @key the verb
     * @value a callable, where arg list is the arguments in the `@mdverb(arg1,arg2)` like `$handler('arg1', 'arg2')`
     *
     */
    public $handlers = [];

    public ?\Tlf\Scrawl $scrawl;

    public function __construct(?\Tlf\Scrawl $scrawl=null){
        $this->scrawl = $scrawl;
    }

    /**
     * Get all `@verbs(arg1, arg2)` 
     * @return array of verb arrays ... the verb array contains `string src`, `string verb`, and `array args`
     */
    public function get_verbs(string $str): array {
        $reg = $this->regs['verb'];
        preg_match_all($reg, $str, $matches, PREG_SET_ORDER);

        $verbs = [];
        foreach ($matches as $m){
            $verb = [];
            $verb['src'] = $m[0];
            $verb['verb'] = $m[1];
            // `$m[2]` is like `arg1, arg2, arg3`, so i expode on `,` & `trim` with array_map
            $verb['args'] = 
                array_map('trim',
                    explode(',',$m[2])
                );
            $verbs[] = $verb;
        }
        return $verbs;
    }

    /**
     * Get all verbs, execute them, and replace them within the doc
     * @param $doc a documentation file's content
     */
    public function replace_all_verbs(string $doc): string{
        $verbs = $this->get_verbs($doc);

        $out = $doc;
        foreach ($verbs as $v){
            $replacement = $this->run_verb($v['src'], $v['verb'], $v['args']);
            $out = str_replace($v['src'], $replacement, $out);
        }
        return $out;
    }

    /**
     * Execute a verb handler and get its output 
     *
     * @param $src the source code for the mdverb, like `@mdverb(arg1,arg)`
     * @param $verb the verb like `mdverb`
     * @param $args an array of the args like `['arg1', 'arg2']`
     *
     * @return string that should replace the `@verb()` call
     */
    public function run_verb(string $src, string $verb, array $args): string {
        if (!isset($this->handlers[$verb])){
            $this->scrawl->warn("MdVerb has no handler", "@$verb()");
            // return '<!-- MdVerb `@'.$verb.'()` has no handler -->';
            return $src;
        }
        $handler = $this->handlers[$verb];
        try {
            $ret = $handler(...$args);
        } catch (\ArgumentCountError $e){
            // @TODO need to use scrawl->warn()
            //
            echo "\n\nARGUMENT COUNT ERROR ... error reporting incomplete ... see code/Ext/MdVerbs.php";
            echo "\n\n";
            throw $e;

//
            // return '';
            // $class = get_class($callable[0]);
            // $method = $callable[1];
            // echo "\n\n";
            // echo "\nCannot call $class->$method() becuase of an argument count error.";
//
            // echo "\n\n";
            // echo "Error caused by: \n";
            // $info = (array)$info;
            // $info['file'] = (array)$info['file'];
            // $info['file']['content'] = '--content is removed from var dump for readability--';
            // print_r($info);
//
            // echo "\n\n";
            // throw $e;
        }

        if (!is_string($ret)){

            $args_str = implode(', ', $args);
            $type = gettype($ret);
            throw new \TypeError("Verbs must return strings. @$verb($args_str) returned $type");
        }
        return $ret;
    }

}